A comparison of axial and lateral PSF profiles of Field II against USTB's Fresnel simulator.

This example shows how to load the data from a Field II simulation into USTB objects, and then beamform it with the USTB routines and compare the axial and lateral PSF profiles of Field II against USTB's Fresnel simulator. This example uses the 128 element L9-4/38 Ultrasonix ultrasound transducer The Field II simulation program (field-ii.dk) should be in MATLAB's path.

This tutorial assumes familiarity with the contents of the 'CPWC simulation with the USTB built-in Fresnel simulator' tutorial. Please feel free to refer back to that for more details.

by Alfonso Rodriguez-Molares alfonso.r.molares@ntnu.no, Ole Marius Hoel Rindal olemarius@olemarius.net and Arun Asokan Nair anair8@jhu.edu 09.05.2017

Contents

Close old plots

close all;

Basic Constants

Our first step is to define some basic constants for our imaging scenario - below, we set the speed of sound in the tissue, sampling frequency and sampling step size in time.

c0=1540;     % Speed of sound [m/s]
fs=100e6;    % Sampling frequency [Hz]
dt=1/fs;     % Sampling step [s]

field II initialisation

Next, we initialize the field II toolbox. Again, this only works if the Field II simulation program (field-ii.dk) is in MATLAB's path. We also pass our set constants to it.

field_init(0);
set_field('c',c0);              % Speed of sound [m/s]
set_field('fs',fs);             % Sampling frequency [Hz]
set_field('use_rectangles',1);  % use rectangular elements
      *------------------------------------------------------------*
      *                                                            *
      *                      F I E L D   I I                       *
      *                                                            *
      *              Simulator for ultrasound systems              *
      *                                                            *
      *             Copyright by Joergen Arendt Jensen             *
      *    Version 3.30, April 5, 2021 (Matlab 2021a version)      *
      *                  Web-site: field-ii.dk                     *
      *                                                            *
      *     This is citationware. Note the terms and conditions    *
      *     for use on the web-site at:                            *
      *               field-ii.dk/?copyright.html                  *
      *  It is illegal to use this program, if the rules in the    *
      *  copyright statement is not followed.                      *
      *------------------------------------------------------------*
Warning:  Remember to set all pulses in apertures for the new sampling frequency

Transducer definition L9-4/38 Ultrasonix, 128-element linear array transducer

Our next step is to define the ultrasound transducer array we are using. For this experiment, we shall use the L9-4/38 128 element Ultrasonix Transducer and set our parameters to match it.

probe = uff.linear_array();
f0                      =5e6;             % Transducer center frequency [Hz]
lambda                  =c0/f0;           % Wavelength [m]
probe.element_height    =6e-3;            % Height of element [m]
probe.pitch             =0.3048e-3;       % probe.pitch [m]
kerf                    =0.035e-3;        % gap between elements [m]
probe.element_width     =probe.pitch-kerf;% Width of element [m]
lens_el                 =19e-3;           % position of the elevation focus
probe.N                 =128;             % Number of elements

Pulse definition

We then define the pulse-echo signal which is done here using the fresnel simulator's pulse structure. We could also use 'Field II' for a more accurate model.

pulse = uff.pulse();
pulse.center_frequency = f0;
pulse.fractional_bandwidth = 0.1;             % probe bandwidth [1]
t0=(-1.0/pulse.fractional_bandwidth /f0): dt : (1.0/pulse.fractional_bandwidth /f0);
excitation=1;
impulse_response=gauspuls(t0, f0, pulse.fractional_bandwidth );
two_ways_ir= conv(conv(impulse_response,impulse_response),excitation)./norm(impulse_response).^2;
if mod(length(impulse_response),2)
    lag=(length(two_ways_ir)-1)/2;
else
    lag=(length(two_ways_ir))/2;
end

% We display the pulse to check that the lag estimation is on place
% (and that the pulse is symmetric)

fig_handle=figure;
plot(((0:(length(two_ways_ir)-1))*dt -lag*dt)*1e6,two_ways_ir); hold on; grid on; axis tight
plot(((0:(length(two_ways_ir)-1))*dt -lag*dt)*1e6,abs(hilbert(two_ways_ir)),'r')
plot([0 0],[min(two_ways_ir) max(two_ways_ir)],'g');
legend('2-ways pulse','Envelope','Estimated lag');
title('2-ways impulse response Field II');
pulse.plot(fig_handle,'','--');

Aperture Objects

Next, we define the the mesh geometry with the help of Field II's xdc_linear_array function.

noSubAz=round(probe.element_width/(lambda/8));        % number of subelements in the azimuth direction
noSubEl=round(probe.element_height/(lambda/8));       % number of subelements in the elevation direction
Th = xdc_linear_array (probe.N, probe.element_width, probe.element_height, kerf, noSubAz, noSubEl, [0 0 Inf]);
Rh = xdc_linear_array (probe.N, probe.element_width, probe.element_height, kerf, noSubAz, noSubEl, [0 0 Inf]);

% We also set the excitation, impulse response and baffle as below:
xdc_excitation (Th, excitation);
xdc_impulse (Th, impulse_response);
xdc_baffle(Th, 0);
xdc_center_focus(Th,[0 0 0]);
xdc_impulse (Rh, impulse_response);
xdc_baffle(Rh, 0);
xdc_center_focus(Rh,[0 0 0]);

Phantom

In our next step, we define our phantom. Here, our phantom is a single point scatterer.

pha=uff.phantom();
pha.sound_speed=1540;            % speed of sound [m/s]
pha.points=[0,  0, 20e-3, 1];    % point scatterer position [m]
fig_handle=pha.plot();
cropat=round(1.1*2*sqrt((max(pha.points(:,1))-min(probe.x))^2+max(pha.points(:,3))^2)/c0/dt);   % maximum time sample, samples after this will be dumped

Output data

We define the variables to store our output data

t_out=0:dt:((cropat-1)*dt);                 % output time vector
STA=zeros(cropat,probe.N,probe.N);    % impulse response channel data

Compute STA signals using Field II

Now, we finally reach the stage where we generate a STA (Synthetic Transmit Aperture) dataset with the help of Field II.

disp('Field II: Computing STA dataset');
wb = waitbar(0, 'Field II: Computing STA dataset');
for n=1:probe.N
    waitbar(n/probe.N, wb);

    % transmit aperture
    xdc_apodization(Th, 0, [zeros(1,n-1) 1 zeros(1,probe.N-n)]);
    xdc_focus_times(Th, 0, zeros(1,probe.N));

    % receive aperture
    xdc_apodization(Rh, 0, ones(1,probe.N));
    xdc_focus_times(Rh, 0, zeros(1,probe.N));

    % do calculation
    [v,t]=calc_scat_multi(Th, Rh, pha.points(1:3), pha.points(4));

    % build the dataset
    STA(1:size(v,1),:,n)=v;

    % Sequence generation
    seq(n)=uff.wave();
    seq(n).probe=probe;
    seq(n).source.xyz=[probe.x(n) probe.y(n) probe.z(n)];
    seq(n).sound_speed=c0;
    seq(n).delay = probe.r(n)/c0 - lag*dt + t; % t0 and center of pulse compensation
    seq(n).apodization = uff.apodization();
    seq(n).apodization.window=uff.window.sta;
    seq(n).apodization.origin=seq(n).source;
end
close(wb);
Index exceeds the number of array elements (1).

Error in STAI_theoretical_PSF (line 128)
disp('Field II: Computing STA dataset');

Channel Data

In this part of the code, we creat a uff data structure to specifically store the captured ultrasound channel data.

channel_data_field_ii = uff.channel_data();
channel_data_field_ii.sampling_frequency = fs;
channel_data_field_ii.sound_speed = c0;
channel_data_field_ii.initial_time = 0;
channel_data_field_ii.pulse = pulse;
channel_data_field_ii.probe = probe;
channel_data_field_ii.sequence = seq;
channel_data_field_ii.data = STA;

Scan

The scan area is defines as a collection of pixels spanning our region of interest. For our example here, we use the linear_scan structure, which is defined with two components: the lateral range and the depth range. scan too has a useful plot method it can call.

sca=uff.linear_scan('x_axis',linspace(-4e-3,4e-3,256).','z_axis', linspace(16e-3,24e-3,256).');

Pipeline

With channel_data and a scan we have all we need to produce an ultrasound image. We now use a USTB structure pipeline, that takes an apodization structure in addition to the channel_data and scan.

pipe=pipeline();
pipe.channel_data=channel_data_field_ii;
pipe.scan=sca;

pipe.receive_apodization.window=uff.window.boxcar;
pipe.receive_apodization.f_number=1.7;
pipe.transmit_apodization.window=uff.window.boxcar;
pipe.transmit_apodization.f_number=1.7;

The beamformer structure allows you to implement different beamformers by combination of multiple built-in processes. By changing the process chain other beamforming sequences can be implemented. It returns yet another UFF structure: beamformed_data.

To achieve the goal of this example, we use delay-and-sum (implemented in the das_mex() process) as well as coherent compounding.

b_data_field_ii =pipe.go({midprocess.das() postprocess.coherent_compounding()});

Compute STA signals using USTB's Fresnel simulator

We also generate STA (Synthetic Transmit Aperture) data with the help of USTB's Fresnel simulator in order to compare it with Field II.

sim=fresnel();

% setting input data
sim.phantom=pha;                % phantom
sim.pulse=pulse;                  % transmitted pulse
sim.probe=probe;                  % probe
sim.sequence=seq;               % beam sequence
sim.sampling_frequency=channel_data_field_ii.sampling_frequency;  % sampling frequency [Hz]

% we launch the simulation
channel_data_fresnel=sim.go();

BEAMFORM data from Fresnel simulation

pipe.channel_data=channel_data_fresnel;
% Delay and sum on receive, then coherent compounding
b_data_fresnel =pipe.go({midprocess.das() postprocess.coherent_compounding()});

Display images

figure(101);
ax1 = subplot(121);
ax2 = subplot(122);

b_data_field_ii.plot(ax1,'Field II Simulation')
b_data_fresnel.plot(ax2,'Fresnel Simulation')

compare lateral profile to sinc

img_field_ii = b_data_field_ii.get_image;
lateral_profile_field_ii=img_field_ii(128,:);
lateral_profile_field_ii=lateral_profile_field_ii-max(lateral_profile_field_ii);

img_fresnel = b_data_fresnel.get_image;
lateral_profile_fresnel=img_fresnel(128,:);
lateral_profile_fresnel=lateral_profile_fresnel-max(lateral_profile_fresnel);

theoretical_profile=20*log10(sinc(1/pipe.receive_apodization.f_number(1)/lambda*b_data_field_ii.scan.x_axis).^2);

figure;
plot(b_data_field_ii.scan.x_axis*1e3,lateral_profile_field_ii); hold all; grid on;
plot(b_data_field_ii.scan.x_axis*1e3,lateral_profile_fresnel,'k');
plot(b_data_field_ii.scan.x_axis*1e3,theoretical_profile,'r');
legend('Field II Simulation','Fresnel Simulation','Theoretical');
xlabel('x [mm]');
ylabel('Amplitude [dB]');
title('Lateral (x-axis) profile ');

compare axial profile

axial_profile_field_ii=img_field_ii(:,128);
axial_profile_field_ii=axial_profile_field_ii-max(axial_profile_field_ii);

axial_profile_fresnel=img_fresnel(:,128);
axial_profile_fresnel=axial_profile_fresnel-max(axial_profile_fresnel);

figure;
plot(b_data_field_ii.scan.x_axis*1e3,axial_profile_field_ii); hold all; grid on;
plot(b_data_field_ii.scan.x_axis*1e3,axial_profile_fresnel,'k');
legend('Field II Simulation','Fresnel Simulation');
xlabel('z [mm]');
ylabel('Amplitude [dB]');
title('Axial (z-axis) profile ');